import os
import httpx

from typing import Optional

from pddnsc.base import BaseOutputProvider


class VscaleDomains(BaseOutputProvider):
    """Домены на vds.selectel.ru (ранее vscale.io)

    Конфигурация:

      - api_token_env: имя переменной окружения, в которой находится токен доступа, по умолчанию `VSCALE_API_TOKEN`
      - api_base: базовый адрес API, по умолчанию `https://api.vscale.io/v1/`
      - domain: имя основного домена, например `example.com`
      - target: имя изменяемой записи, например `www` (чтобы изменить `www.example.com`)
      - ttl: время жизни записи, по умолчанию 3600
      - save_ipv4: сохранять ли ipv4 адрес (A запись)
      - save_ipv6: сохранять ли ipv6 адрес (AAAA запись)

      (если нет ни одного из [save_ipv4, save_ipv6] то сохраняются оба адреса)
    """

    def post_init(self):
        token = self.get_api_token()
        if not token:
            raise KeyError("no api token, use env VSCALE_API_TOKEN")
        self.__headers = {"X-Token": token}
        self.api_base = self.config.get("api_base", "https://api.vscale.io/v1/")
        self.ttl = self.config.get("ttl", 3600)
        self.domain = self.config["domain"]
        target = self.config["target"]
        self.target = f"{target}.{self.domain}"

        self.save_ipv4 = self.config.get("ipv4", False)
        self.save_ipv6 = self.config.get("ipv6", False)
        if "ipv4" not in self.config and "ipv6" not in self.config:
            self.save_ipv4 = self.save_ipv6 = True

    def get_api_token(self) -> str:
        """получение API токена"""
        token_env = self.config.get("api_token_env", "VSCALE_API_TOKEN")
        return os.environ[token_env]

    async def find_domain_id(self, client: httpx.AsyncClient) -> Optional[int]:
        """поиск ID домена"""
        response = await client.get("/domains/")
        if response.is_success:
            data = response.json()
            if isinstance(data, list):
                for entry in data:
                    if entry["name"] == self.domain:
                        return entry["id"]
            else:
                raise TypeError("failed to find domain id, unexpected response type")
        else:
            raise ValueError(f"failed to find domain id, code: {response.status_code}")

    async def find_record(
        self, client: httpx.AsyncClient, domain_id: int, record_type: str
    ) -> Optional[int]:
        """поиск ID записи"""
        response = await client.get(
            f"/domains/{domain_id}/records/",
        )
        if response.is_success:
            data = response.json()
            if isinstance(data, list):
                for entry in data:
                    if entry["name"] == self.target and entry["type"] == record_type:
                        return entry["id"]
        else:
            raise RuntimeError(
                f"error list records {domain_id=}: ", response.status_code
            )

    async def get_record_value(
        self, client: httpx.AsyncClient, domain_id: int, record_id: int
    ) -> str:
        """получение действующего значения записи"""
        response = await client.get(f"/domains/{domain_id}/records/{record_id}")
        if response.is_success:
            data = response.json()
            if isinstance(data, dict):
                return data["content"]

    async def change_record(
        self,
        client: httpx.AsyncClient,
        domain_id: int,
        record_id: int,
        record_type: str,
        value: str,
    ):
        """изменение записи"""
        data = {
            "content": value,
            "name": self.target,
            "ttl": self.ttl,
            "type": record_type,
            "id": record_id,
        }

        response = await client.put(
            f"/domains/{domain_id}/records/{record_id}",
            json=data,
        )
        if not response.is_success:
            raise RuntimeError(
                f"failed to change record: {self.target=},{domain_id=}, {record_id=}, {record_type=}, {value=}"
            )

    async def create_record(
        self, client: httpx.AsyncClient, domain_id: int, record_type: str, value: str
    ):
        """создание новой записи"""
        data = {
            "content": value,
            "name": self.target,
            "ttl": self.ttl,
            "type": record_type,
        }
        response = await client.post(
            f"/domains/{domain_id}/records/",
            json=data,
        )
        if not response.is_success:
            raise RuntimeError(
                f"failed to create record: {self.target=},{domain_id=}, {record_type=}, {value=}, {response.status_code=}"
            )

    async def set_addrs_imp(self, source_provider: str, addr_v4: str, addr_v6: str):
        save_addrs = []
        if addr_v4 and self.save_ipv4:
            save_addrs.append(("A", addr_v4))

        if addr_v6 and self.save_ipv6:
            save_addrs.append(("AAAA", addr_v6))

        transport = self.best_transport(addr_v4, addr_v6)
        async with httpx.AsyncClient(
            transport=transport,
            base_url=self.api_base,
            headers=self.__headers,
            http2=True,
        ) as client:
            domain_id = await self.find_domain_id(client)
            for record_type, value in save_addrs:
                record_id = await self.find_record(client, domain_id, record_type)
                if record_id:
                    old_value = await self.get_record_value(
                        client, domain_id, record_id
                    )
                    if old_value != value:
                        await self.change_record(
                            client, domain_id, record_id, record_type, value
                        )
                    else:
                        print(
                            f"vscale: skip record change ({record_type=}), value equal"
                        )
                else:
                    await self.create_record(client, domain_id, record_type, value)
